Skip to content

Conversation

@TimMonko
Copy link
Member

@TimMonko TimMonko commented Dec 2, 2025

This pull request refactors how the code detects and handles the presence of the napari library for threaded batch operations. Instead of checking for napari at module import time, the code now tries to import napari only when needed, improving flexibility and testability. The test suite is also updated to simulate the absence of napari by monkeypatching the import mechanism.

Refactoring napari detection and threading logic:

  • Removed the module-level HAS_NAPARI flag and deferred importing napari to runtime, allowing the code to try using napari threading if available and fall back to standard threading otherwise (src/nbatch/_runner.py) [1] [2].
  • Updated the cancel method to check for a quit method on the worker instead of relying on the HAS_NAPARI flag, making cancellation logic more robust and decoupled from napari-specific checks (src/nbatch/_runner.py).
  • Moved the import of create_worker into the _run_napari_threaded method so it only occurs when napari threading is actually used (src/nbatch/_runner.py).

Testing improvements:

  • Updated the threaded fallback test to simulate napari's absence by monkeypatching Python's import mechanism, ensuring the fallback path is tested regardless of napari's actual installation (tests/test_runner.py) [1] [2].

@codecov
Copy link

codecov bot commented Dec 2, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes import times by deferring napari imports until they are actually needed. The key change is moving napari availability checking from module load time to runtime, reducing startup overhead when napari is not immediately required.

Key Changes:

  • Implements lazy loading for napari via a cached _check_napari() function
  • Renames HAS_NAPARI to _HAS_NAPARI to indicate it's a private implementation detail
  • Moves the create_worker import from module-level into the _run_napari_threaded method

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/nbatch/_runner.py Implements lazy napari checking with _check_napari() function and defers create_worker import to point of use
tests/test_runner.py Updates test to reference renamed _HAS_NAPARI variable

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self._was_cancelled = True # Set immediately for threaded cases
# Store local reference to avoid race condition with _handle_finished
worker = self._worker if HAS_NAPARI else None
worker = self._worker if _check_napari() else None
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is incorrect. It checks if napari is currently available to determine if the worker is a napari worker, but the worker type depends on what was available when run() was called, not what's available now. If napari becomes unavailable between run() and cancel() (or vice versa), this will fail.

Instead, check if the worker has a quit() method:

worker = self._worker if self._worker is not None and hasattr(self._worker, 'quit') else None
Suggested change
worker = self._worker if _check_napari() else None
worker = self._worker if self._worker is not None and hasattr(self._worker, 'quit') else None

Copilot uses AI. Check for mistakes.
Comment on lines 30 to 40
def _check_napari() -> bool:
"""Lazily check for napari availability."""
global _HAS_NAPARI
if _HAS_NAPARI is None:
try:
import napari.qt.threading # noqa: F401

_HAS_NAPARI = True
except ImportError:
_HAS_NAPARI = False
return _HAS_NAPARI
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: Multiple threads could simultaneously check _HAS_NAPARI is None and both attempt to import napari. While this won't cause incorrect behavior (both will set the same value), it's inefficient and could cause multiple import attempts.

Consider using a lock or threading.Lock() to ensure thread-safe initialization:

_NAPARI_LOCK = threading.Lock()

def _check_napari() -> bool:
    """Lazily check for napari availability."""
    global _HAS_NAPARI
    if _HAS_NAPARI is None:
        with _NAPARI_LOCK:
            # Double-check after acquiring lock
            if _HAS_NAPARI is None:
                try:
                    import napari.qt.threading  # noqa: F401
                    _HAS_NAPARI = True
                except ImportError:
                    _HAS_NAPARI = False
    return _HAS_NAPARI

Copilot uses AI. Check for mistakes.
import nbatch._runner as runner_module

monkeypatch.setattr(runner_module, 'HAS_NAPARI', False)
monkeypatch.setattr(runner_module, '_HAS_NAPARI', False)
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential test issue: The test monkeypatches _HAS_NAPARI to force the fallback path, but this only works if _check_napari() hasn't been called yet. If any prior test already called _check_napari(), the cached value would be used and this monkeypatch would have no effect.

To make this test more robust, also patch the _check_napari function itself:

monkeypatch.setattr(runner_module, '_HAS_NAPARI', False)
monkeypatch.setattr(runner_module, '_check_napari', lambda: False)

Or reset the cache before patching:

runner_module._HAS_NAPARI = None
monkeypatch.setattr(runner_module, '_HAS_NAPARI', False)
Suggested change
monkeypatch.setattr(runner_module, '_HAS_NAPARI', False)
monkeypatch.setattr(runner_module, '_HAS_NAPARI', False)
monkeypatch.setattr(runner_module, '_check_napari', lambda: False)

Copilot uses AI. Check for mistakes.
@TimMonko TimMonko merged commit f94c191 into ndev-kit:main Dec 2, 2025
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant